///////////////////////////////////////////////////////////////////////////////
//
// Licensed under the MIT License
//
// Copyright (c) 2010 Chris Colinsky
// All Rights Reserved
//
// version	0.5
// http://code.google.com/p/sugarcookie/
// http://code.google.com/p/sugarcookie/wiki/License
//
////////////////////////////////////////////////////////////////////////////////

package sugarcookie.framework {
	import com.asual.swfaddress.*;
	
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.utils.getDefinitionByName;
	import flash.utils.setTimeout;
	
	import mx.collections.IViewCursor;
	import mx.utils.ObjectUtil;
	
	import sugarcookie.collections.EnhancedArrayCollection;
	import sugarcookie.display.IUIDisplayObject;
	import sugarcookie.display.MovieClipBase;
	import sugarcookie.error.ApplicationError;
	import sugarcookie.events.GenericEvent;
	import sugarcookie.framework.IModel;
	import sugarcookie.framework.Model;
	import sugarcookie.framework.vo.NavData;
	import sugarcookie.notifications.Notification;
	import sugarcookie.preloaders.IPreloader;
	import sugarcookie.utils.IObserver;
	import sugarcookie.utils.Observable;

	/**
	 * Manages subclasses of the View class that implement IView.
	 */
	public class ViewStack extends MovieClipBase implements IViewStack {
		public static const PROGRESS:String = "onProgress";
		public static const PROGRESS_COMPLETE:String = "onProgressComplete";
		public static const PRE_TRANSITION:String = "preTransition";
		public static const POST_TRANSITION:String = "postNotification";
		
		private var _stackContainer:Sprite;
		private var _views:EnhancedArrayCollection;
		private var _cursor:IViewCursor;
		private var _globalUIComponents:Sprite;
		private var _backgroundComponents:Sprite;
		private var _isTransitionActive:Boolean = false;
		private var _isIntroActive:Boolean = false;
		private var _viewToBeReset:IView;
		private var _numMasks:int = 0;
		
		protected var _notifier:Observable;
		protected var _viewMap:Array;
		protected var _preloader:IPreloader;
		protected var _currNavData:NavData;
		protected var _prevNavData:Object;
		protected var _dm:IModel;
		protected var _isBrowserHistory:Boolean;
		
		public function ViewStack(dmClassDef:String) {
			super();
			_dm = getDefinitionByName(dmClassDef).getInst();
			
			_backgroundComponents = new Sprite();
			addChild(_backgroundComponents);
			
			_stackContainer = new Sprite();
			addChild(_stackContainer);
			
			_globalUIComponents = new Sprite();
			addChild(_globalUIComponents);
			
			_views = new EnhancedArrayCollection();
			_cursor = _views.createCursor();
			
			_notifier = new Observable(this);
			
			addEventListener(Event.ADDED_TO_STAGE,onAddedToStage);
			addEventListener(PROGRESS, onProgressHandler);
			addEventListener(PROGRESS_COMPLETE, onProgressComplete);
			
			//Link.init(this);
			
			_viewMap = Model.VIEW_MAP;
			
			initViewMap();
		}
		
		final public function addObserver(observer:IObserver,notificationType:String=null):Boolean {
			return _notifier.addObserver(observer,notificationType);
		}
		
		final public function removeObserver(observer:IObserver,notificationType:String=null):Boolean {
			return _notifier.removeObserver(observer);
		}
		
		protected function notifyObservers(type:String,data:Object=null):void {
			_notifier.notifyObservers(type,data);
		}
		
		final public function clearObservers():void {
			_notifier.clearObservers();
		}
		
		final public function countObservers():Number {
			return _notifier.countObservers();
		}
		
		final public function get useLogging():Boolean { return _notifier.useLogging; }
		final public function set useLogging(value:Boolean):void { _notifier.useLogging = value; }
		
		final public function get stageOrigin():Point {
			var _point:Point = new Point(-1*x,-1*y);
			return _point;
		}
		
		final public function get currentNavData():NavData {
			return _currNavData;
		}
		
		final public function get activeView():IView {
			return View(_views.getItemAtKey(_currNavData.view));
		}
		
		final public function getViewById(id:String):IView {
			if (_views.containsKey(id)) return View(_views.getItemAtKey(id));
			else return null;
		}
		
		public function track(data:Object):void {
			//to be overridden by subclass
			_log.info("ViewStack.track() can be overridden by subclass.  do any custom set-up needed");
		}
		
		protected function init():void {
			//to be overridden by subclass
			_log.info("ViewStack.init() can be overridden by subclass.  do any custom set-up needed");
		}
		
		private function onAddedToStage(evt:Event):void {
			removeEventListener(Event.ADDED_TO_STAGE,onAddedToStage);
			onAdded();
			checkForIntro();
		}
		
		protected function onAdded():void {
			_log.info("ViewStack.onAdded() - do any custom set-up that requires the stage object.");
		}
		
		protected function queueIntro():void {
			//to be overridden by subclass
			_log.info("ViewStack.queueIntro() - start up your own intro.");
		}
		
		protected function initDisplay():void {
			//to be overridden by subclass
			_log.info("ViewStack.initDisplay() - your last chance to do something before the first view is loaded.");
		}
		
		protected function preTransitionHandler(nd:NavData):void {
			//to be overridden by subclass
			_log.info("ViewStack.preTransitionHandler() - opportunity to trigger something before the transition starts to the next view.");
		}
		
		protected function postTransitionHandler(nd:NavData):void {
			//to be overridden by subclass
			_log.info("ViewStack.postTransitionHandler() - opportunity to trigger something after the new view's onShow was called.");
		}
		
		protected function onProgressHandler(evt:GenericEvent):void {
			//to be overridden by subclass
			_log.info("ViewStack.onProgressHandler() is used to handle bubbled progress events to be used with a centralized progress loader.");
		}
		
		protected function onProgressComplete(evt:GenericEvent):void {
			//to be overridden by subclass
			_log.info("ViewStack.onProgressComplete() is used to handle bubbled progress events to be used with a centralized progress loader.");
		}
		
		final protected function setViewMask(viewId:String, msk:DisplayObject):void {
			_stackContainer.addChild(msk);
			var view:View = View(_views.getItemAtKey(viewId));
			view.mask = msk;
			_numMasks++;
		}
		
		final protected function setDisplayCoords(_x:Number,_y:Number):void {
			x = _x;
			y = _y;
		}
		
		final protected function setBackgroundCoords(_x:Number,_y:Number):void {
			_backgroundComponents.x = _x;
			_backgroundComponents.y = _y;
		}
		
		final protected function setBackgroundDimension(_w:Number,_h:Number):void {
			_backgroundComponents.width = _w;
			_backgroundComponents.height = _h;
		}
		
		final protected function addBackgroundComponent(bgc:IUIDisplayObject,depth:int=-1):DisplayObject {
			bgc.UIDisplayRoot = this;
			if (depth==-1) return _backgroundComponents.addChild(DisplayObject(bgc));
			else return _backgroundComponents.addChildAt(DisplayObject(bgc),depth);
		}
		
		final protected function removeBackgroundComponent(bgc:DisplayObject):DisplayObject {
			return _backgroundComponents.removeChild(bgc);
		}
		
		final protected function addUIComponent(uic:IUIDisplayObject,depth:int=-1):IUIDisplayObject {
			uic.UIDisplayRoot = this;
			if (depth==-1) return IUIDisplayObject(_globalUIComponents.addChild(DisplayObject(uic)));
			else return IUIDisplayObject(_globalUIComponents.addChildAt(DisplayObject(uic),depth));
		}
		
		final protected function removeUIComponent(uic:DisplayObject):DisplayObject {
			return _globalUIComponents.removeChild(uic);
		}
		
		private function initViewMap():void {
			//auto register views definied in config.xml
			var viewmap:Array = _viewMap;
			for (var i:String in viewmap) {
				if (viewmap[i].viewClassDef==null) continue;
				var viewClass:Class = Class(getDefinitionByName(viewmap[i].viewClassDef));
				var view:IView = IView(new viewClass());
				view.id = viewmap[i].id;
				registerView(i,view);
			}
			
			init();
		}
		
		final protected function registerView(id:String,view:IView):void {
			if (_views.containsKey(id)) {
				throw new ApplicationError("View already exists");
				return;
			} else {
				_views.addItemWithKey(id,view);
				_log.debug("registering new view: "+id);
			}
		}
		
		final public function initFirstView():void {
			_log.debug("ViewStack.initFirstView()");
			initDisplay();
			_isIntroActive = false;
			SWFAddress.setValue(_dm.initDeepLink);
			SWFAddress.addEventListener(SWFAddressEvent.EXTERNAL_CHANGE, handleExternalSWFAddressChange);
			SWFAddress.addEventListener(SWFAddressEvent.INTERNAL_CHANGE, handleInternalSWFAddressChange);
		}
		
		private function checkForIntro():void {
			_log.debug("ViewStack.checkForIntro(): _dm.hasIntro="+_dm.hasIntro);
			if (_dm.hasIntro) {
				_isIntroActive = true;
				queueIntro();
			} else {
				initFirstView();
			}
		}
		
		public function navigate(navData:NavData):void {
			_prevNavData = ObjectUtil.copy(_currNavData);
			_currNavData = navData;
			_currNavData.prevViewId = _prevNavData.view;
			
			notifyObservers(PRE_TRANSITION,_currNavData);
			
			if (_currNavData.view == _prevNavData.view) {
				setSWFAddress();
			} else {
				if (_isTransitionActive || _isIntroActive) return;
				
				_isTransitionActive = true;
				IView(_views.getItemAtKey(_prevNavData.view)).hide();
			}
		}
		
		private function handleInternalSWFAddressChange(evt:SWFAddressEvent):void {
			_log.debug("handleInternalSWFAddressChange - recieved: "+ObjectUtil.toString(evt));
			setTitle();
			setView();
		}
		
		private function handleExternalSWFAddressChange(evt:SWFAddressEvent):void {
			_log.debug("handleExternalSWFAddressChange - recieved: "+ObjectUtil.toString(evt));
			
			if (_currNavData!=null) {
				_isBrowserHistory = true;
				_prevNavData = ObjectUtil.copy(_currNavData);
				_currNavData = NavData.parseNavData(evt.value);
				
				if (_currNavData.view==_prevNavData.view) {
					setTitle();
					setView();
					return;
				}
				
				if (_isTransitionActive || _isIntroActive) return;
				_isTransitionActive = true;
				notifyObservers(PRE_TRANSITION,_currNavData);
				IView(_views.getItemAtKey(_prevNavData.view)).hide();
				
			} else {
				_prevNavData = _currNavData = NavData.parseNavData(evt.value);
				_currNavData.initView=true;
				setTitle();
				setView();
			}
		}
		
		public function viewHideComplete(target:IView):void {
			_viewToBeReset = target;
			_log.debug("viewHideComplete: _isBrowserHistory: "+_isBrowserHistory);
			if (_isBrowserHistory) {
				_isBrowserHistory = false;
				setTitle();
				setView();
			} else {
				setSWFAddress();
			}
		}
		
		public function viewShowComplete(target:IView):void {
			if (_viewToBeReset != null) _viewToBeReset.reset();
			
			notifyObservers(POST_TRANSITION,_currNavData);
			
			if (Sprite(_views.getItemAtKey(_currNavData.view))!=Sprite(target)) {
				target.hide();
			}
			else { _isTransitionActive = false; }
		}
		
		protected function constructTitle():String {
			//to be overridden in subclass
			//works off of _currNavData to format a title
			_log.info("ViewStack.constructTitle() is used to display custom titles for each SWFAddress state change.");
			if (_dm.defaultPageTitle!=null) return _dm.defaultPageTitle;
			else return "";
		}
		
		final protected function setTitle():void {
			if (_dm.defaultPageTitle!=null) SWFAddress.setTitle(constructTitle());
		}
		
		private function setSWFAddress():void {
           _log.debug("Setting SWFAdress to value: "+_currNavData.value);
		   if (_currNavData.value==_prevNavData.value) setView();
		   else SWFAddress.setValue(_currNavData.value);
        }
		
		private function setView():void {
			_log.debug("setting new view: "+_currNavData.view);
			
			if (!_stackContainer.contains(Sprite(_views.getItemAtKey(_currNavData.view)))) {
				var view:View = View(_views.getItemAtKey(_currNavData.view));
				view.dataManager = _dm;
				view.UIDisplayRoot = this;
				_stackContainer.addChild(view);
			}
			if (_viewMap[_currNavData.view].type=="external") _currNavData.path = _dm.getPath("swf") + _viewMap[_currNavData.view].src;
			
			_stackContainer.setChildIndex(DisplayObject(_views.getItemAtKey(_currNavData.view)),_stackContainer.numChildren-1);
			IView(_views.getItemAtKey(_currNavData.view)).show(_currNavData);
		}
	}
}